/* 
 * File:   Cliente.cpp
 * Author: Maximiliano Monzon (82341)
 * 
 * Created on October 20, 2012, 9:05 AM
 */
#include <cstdio>
#include <iostream>
#include <sstream>
#include <fstream>
#include "client_Cliente.h"
#include "client_Operacion.h"
#include "client_OperacionEco.h"
#include "client_OperacionCargaArchivo.h"
#include "common_ProtocoloConstantes.h"
#include "common_Excepcion.h"
#include "common_Descriptor.h"
#include "common_RepositorioBuilder.h"
#include "common_TokenFactory.h"

using namespace std;
using namespace TP;
using namespace TP::Archivos;
using namespace TP::Operaciones;
using namespace TP::Comunicacion;

Cliente::Cliente(ClientConfFileManager* pConfigurationManager, std::ostream& pLog): _log (pLog) {
    
    _config = pConfigurationManager;
    _socket = new SocketAsincronico (pConfigurationManager->GetIpServidor(), pConfigurationManager->GetPuertoServidor());
    _observadorSocket = new ObservadorSocket(_socket);
    _fileService = new FileServiceProxy(_config->GetUsuario(), _socket);
    _ecoService = new EcoServiceProxy(_socket);
    _repositorio = NULL;
    _repositorioPoller = NULL;
}

Cliente::~Cliente() {
    
    delete _socket; 
    _socket = NULL;
    
    delete _observadorSocket;
    _observadorSocket = NULL;
    
    delete _repositorio;
    _repositorio = NULL;
    
    delete _repositorioPoller;
    _repositorioPoller = NULL;
    
    _config = NULL;
}

void Cliente::Iniciar() throw (Excepcion&){

    try
    {   
        /* Inicio la conexion con el servidor.
         */
        _socket->Iniciar();
        
        /* Ejecuto la autenticacion.
         */
        if (!this->Autenticar())
        {
            throw Excepcion ("Error de autenticacion.");
        }
        
        /* Se inicia la escucha de eventos por parte del servidor.
         */
        _observadorSocket->SetObservador((IObservadorSocketManejadorEvento*) this);
        _observadorSocket->Iniciar();
    
        /* Se inicia el repositorio.
         */
        this->IniciarRepositorio();
    
        /* Se inicia el poller del repositorio.
         */
        this->IniciarRepositorioPoller();
        
    }
    catch (Excepcion &pEx)
    {
        pEx.expandirMensaje("No se puede inicar el cliente.");
        throw;
    }
}

void Cliente::Finalizar() throw (Excepcion&){
    try
    {
        _repositorioPoller->Finalizar();
        _observadorSocket->Finalizar();
        _socket->Finalizar();
        
        this->PersistirRepositorio();
    }
    catch (Excepcion& pEx)
    {
        pEx.expandirMensaje("No se puede finalizar el cliente.");
        throw;
    }
}

/* notificacionErrorConexion:
 *      Maneja los mensajes de error de conexion por parte del socket.
 */
void Cliente::notificarErrorConexion (IEnviante* pEnviante, const string& pMensaje){
    cout << endl << pMensaje << endl;
}

/* notificarMensajeEntrante:
 *      Maneja la notificacion de un mensaje entrante a traves del socket.
 */
void Cliente::notificarMensajeEntranteServidor(IEnviante* pEnviante, const string& pMensaje) {
    
    try
    {
        vector<string> mensaje = hidratarMensaje(pMensaje);
        vector<IRespuestaServidorManejador*>::iterator it = _respuestaServidorManejadores.begin();
        
        while (it < _respuestaServidorManejadores.end())
        {
            if ((*it)->asociado(mensaje))
            {
                (*it)->procesarRespuesta(mensaje);
                break;
            }            
            ++it;
        }
    }
    catch (Excepcion &ex)
    {
        cerr << endl << ex.what() << endl;
    }
}

vector<string> Cliente::hidratarMensaje(const string& pMensaje) throw (Excepcion&) {
        
    // Hidrato el comando.
    try
    {
        std::stringstream ssParametros (pMensaje);
        string strParametro = "";
        vector<string> _mensaje;
        
        while (std::getline(ssParametros, strParametro, SEPARADOR_CAMPO))
        {
            std::stringstream ssHelper;
            ssHelper << strParametro;
            strParametro = "";
            ssHelper >> strParametro;

            if (strParametro.length () > 0)
            {
                _mensaje.push_back(strParametro);
            }
        }
        
        return _mensaje;
    }
    catch (int &)
    {
        throw;
    }
}

bool Cliente::Autenticar(){
    
    bool autenticado = false;
    
    // Envia la credencial al servidor.
    string credencial = GetCredencial ();
    _socket->Escribir((uint8_t*) credencial.c_str(), credencial.length());

    // Obtiene la respuesta de autenticacion.
    
    uint8_t* buffer = NULL;
    uint32_t bufferLongitud = 0;
    uint32_t intentos = 10;
    
    while (0 == bufferLongitud && intentos > 0)
    {
        usleep (1000000);
        _socket->Leer(buffer, bufferLongitud);
        intentos--;
    }

    // Verifica resultado de autenticacion.
    if (bufferLongitud > 0)
    {
        string respuesta = "";
        for (size_t i = 0; i < bufferLongitud; i++) respuesta += buffer[i];
        Descriptor descriptor (SEPARADOR_CAMPO);
        descriptor.parse(respuesta);
        
        autenticado = (AUTENTICACION_OK == descriptor.data(0));
    }
    
    return autenticado;
}

string Cliente::GetCredencial (){
    
    string credencial = USUARIO_CREDENCIAL;
    
    credencial += SEPARADOR_CAMPO;
    credencial += _config->GetUsuario();
    credencial += SEPARADOR_CAMPO;
    credencial += _config->GetPassword();
    
    return credencial;
}

void Cliente::IniciarRepositorio(){
    
    /* Levanta el repositorio serializado.  En caso de no encontrar un archivo
     * que contenga la serializacion del repositorio, reprocesa el directorio
     * AU generando el repositorio y serializa la instancia.
     */
    
    ifstream file (NOMBRE_ARCHIVO_REPOSITORIO_SERIALIZADO);
    if (file.is_open())
    {
        std::stringstream strstm;
        strstm << file.rdbuf();
        file.close();
        
        _repositorio = Repositorio::Parse(strstm.str());
    }
    else
    {
        RepositorioBuilder builder;
        _repositorio = builder.ConstruirDeRutaDirectorio(_config->GetPathAU());
    }
}

void Cliente::PersistirRepositorio (){

    /* Serializar el repositorio local.
     */
    
    ofstream file (NOMBRE_ARCHIVO_REPOSITORIO_SERIALIZADO, ios::trunc);

    if (file.is_open())
    {
        file << _repositorio->Serializar();
        file.close();
    }
}

string Cliente::GetDirectorioAPolear(){
    return _config->GetPathAU();
}

void Cliente::IniciarRepositorioPoller() throw (Excepcion&) {

    try
    {
        /* Instancio  el poller del repositorio.
         */
        _repositorioPoller = new RepositorioPoller(_repositorio, _config->GetIntervaloPolling());

        /* Instancio la operacion de sincronizacion del repositorio con el servidor
         * que ejecuta el poller y configuro el poller de repositorio con dicha 
         * operacion de sincronizacion.
         */
        OperacionSincronizacionRepositorio* operacion = new OperacionSincronizacionRepositorio(TokenFactory::Crear(), _fileService);
        _repositorioPoller->SetOperacionSincronizacionRepositorio (operacion);

        /* Declaro la operacion de sincronizacion como manejador de eventos del servidor.
         */
        _respuestaServidorManejadores.push_back((IRespuestaServidorManejador*) operacion);

        /* Inicio la ejecucion del poller del repositorio.
         */
        _repositorioPoller->Iniciar();
    }
    catch (Excepcion &ex)
    {
        throw;
    }
}